#include <kernel.h>

struct uthread_priv_s
{
	 uintptr_t entry;
	 uintptr_t stack;

	 int       in_msg;
	 int       msg_busy;
	 uintptr_t msg_disp;
	 char     *msg_disp_stk;
	 void    **msg_buf;
	 uintptr_t msg_buf_cap;
	 uintptr_t msg_head;
	 uintptr_t msg_tail;

	 uintptr_t upriv;
};

struct uthread_msg_disp_arg_s
{
	 void    **msg_buf;
	 uintptr_t msg_buf_cap;
	 uintptr_t msg_head;
	 uintptr_t msg_tail;
	 int       from_trapframe;
	 union
	 {
		  struct trapframe_s tf;
		  struct tf_sys_s    sys;
	 };
};

static void
_uthread_entry(void *_priv)
{
	 struct uthread_priv_s *priv = (struct uthread_priv_s *)_priv;
	 proc_priv_set(priv);
	 proc_self_type_set(PROC_TYPE_UTHREAD);
	 
	 ekf_jump_user((void *)priv->entry, (void *)priv->stack, read_eflags() | FL_IF);
}

#define UEVENT_HASH_SIZE 19997UL
#define UEVENT_HASH_MUL  39997UL

struct uevent_hash_item_s
{
	 uint32_t     u;
	 proc_event_t e;
	 struct uevent_hash_item_s *next;
};

struct uevent_hash_item_s *uevent_hash_list_head[UEVENT_HASH_SIZE];

int
uthread_init(void)
{
	 int i;
	 for (i = 0; i != UEVENT_HASH_SIZE; ++ i)
		  uevent_hash_list_head[i] = 0;
	 
	 return 0;
}

proc_t
uthread_create(const char *name,
			   uint32_t level, uint32_t kstack_size, uintptr_t vpt,
			   uintptr_t entry, uintptr_t upriv, uintptr_t ustack_top,
			   unsigned int msg_buf_capacity)
{
	 struct uthread_priv_s *priv = (struct uthread_priv_s *)ekf_alloc(sizeof(struct uthread_priv_s));
	 if (priv == NULL) return NULL;

	 proc_t r = proc_create(name, level, kstack_size, vpt, _uthread_entry, priv);
	 if (r == NULL)
	 {
		  ekf_free(priv);
		  return NULL;
	 }

	 priv->entry = entry;
	 priv->stack = ustack_top;
	 priv->msg_disp = 0;

	 if (msg_buf_capacity < 1024) msg_buf_capacity = 1024;
	 int msg_buf_psize  = (msg_buf_capacity * sizeof(void *) + PAGE_SIZE - 1) >> PAGE_SHIFT;
	 msg_buf_psize     += 2;		/* MAGIC! For the dispatcher stack */
	 priv->msg_disp_stk = (char *)ekf_balloc(msg_buf_psize << PAGE_SHIFT);

	 if (priv->msg_disp_stk == NULL)
	 {
		  proc_destroy(r);
		  ekf_free(priv);

		  return NULL;
	 }

	 int i;
	 for (i = 0; i != msg_buf_psize; ++ i)
	 {
		  char *page = priv->msg_disp_stk + (i << PAGE_SHIFT);
		  ekf_vpt_set(page, ekf_vpt_get(page) | PTE_U);
	 }

	 priv->msg_buf      = (void **)(priv->msg_disp_stk + 2 * PAGE_SIZE);
	 priv->msg_disp_stk = (char *)priv->msg_buf;

	 struct uthread_msg_disp_arg_s *stk = (struct uthread_msg_disp_arg_s *)
		  (priv->msg_disp_stk - sizeof(struct uthread_msg_disp_arg_s));
	 	 
	 priv->in_msg   = 0;
	 priv->msg_busy = 1;
	 priv->msg_buf_cap = msg_buf_capacity;
	 priv->msg_head = 0;
	 priv->msg_tail = 0;
	 priv->upriv = upriv;

	 return r;
}

static void
uevent_handler(struct uevent_hash_item_s *item)
{
	 uthread_kmsg_enqueue(item->u);
}

proc_event_t
uevent_get(uint32_t ue)
{
	 uint32_t h = ue * UEVENT_HASH_MUL % UEVENT_HASH_SIZE;
	 struct uevent_hash_item_s *cur;	 
	 proc_event_t e = NULL;

	 intr_irq_save();
	 
	 cur = uevent_hash_list_head[h];
	 while (cur != NULL)
	 {
		  if (cur->u == ue) break;
		  cur = cur->next;
	 }

	 if (cur != NULL) e = cur->e;
	 else
	 {
		  /* XXX: resource alloc here */
		  cur = (struct uevent_hash_item_s *)ekf_alloc(sizeof(struct uevent_hash_item_s));
		  cur->u = ue;
		  e = cur->e = proc_event_local_create(1, (void(*)(void *))uevent_handler, cur);
		  cur->next = uevent_hash_list_head[h];

		  uevent_hash_list_head[h] = cur;
	 }

	 intr_irq_restore();
	 
	 return e;
}

proc_event_t
uevent_find(uint32_t ue)
{
	 uint32_t h = ue * UEVENT_HASH_MUL % UEVENT_HASH_SIZE;
	 struct uevent_hash_item_s *cur;
	 proc_event_t e = NULL;

	 intr_irq_save();
	 cur = uevent_hash_list_head[h];
	 while (cur != NULL)
	 {
		  if (cur->u == ue) break;
		  cur = cur->next;
	 }

	 if (cur != NULL) e = cur->e;
	 
	 intr_irq_restore();
	 
	 return e;
}

void
uthread_trapframe_switch_back_hook(struct trapframe_s *tf)
{
	 struct uthread_priv_s *priv = (struct uthread_priv_s *)proc_priv_get();
	 proc_event_process_irq_save();
	 *((uint32_t *)CPUPRIV) = priv->upriv;
	 if (priv->msg_tail != priv->msg_head && !priv->in_msg && !priv->msg_busy)
	 {
		  priv->in_msg = 1;
		  struct uthread_msg_disp_arg_s *stk = (struct uthread_msg_disp_arg_s *)
			   (priv->msg_disp_stk - sizeof(struct uthread_msg_disp_arg_s));

		  stk->from_trapframe = 1;
		  stk->msg_head    = priv->msg_head;
		  stk->msg_tail    = priv->msg_tail;
		  stk->msg_buf     = priv->msg_buf;
		  stk->msg_buf_cap = priv->msg_buf_cap;

		  if (tf != NULL) memmove(&stk->tf, tf, sizeof(struct trapframe_s));
		  ekf_jump_user((void *)priv->msg_disp,
						(void *)((uintptr_t)stk - 4), read_eflags() | FL_IF);
	 }
}

void
uthread_trap_sys_switch_back_hook(struct tf_sys_s *tf)
{
	 struct uthread_priv_s *priv = (struct uthread_priv_s *)proc_priv_get();
	 proc_event_process_irq_save();
	 *((uint32_t *)CPUPRIV) = priv->upriv;
	 if (priv->msg_tail != priv->msg_head && !priv->in_msg && !priv->msg_busy)
	 {
		  priv->in_msg = 1;
		  struct uthread_msg_disp_arg_s *stk = (struct uthread_msg_disp_arg_s *)
			   (priv->msg_disp_stk - sizeof(struct uthread_msg_disp_arg_s));

		  stk->from_trapframe = 0;
		  stk->msg_head    = priv->msg_head;
		  stk->msg_tail    = priv->msg_tail;
		  stk->msg_buf     = priv->msg_buf;
		  stk->msg_buf_cap = priv->msg_buf_cap;

		  /* MAGIC MOVE, TO EXPLAIN LATER */
		  if (tf != NULL) memmove(&stk->sys, tf, sizeof(struct tf_sys_s));
		  ekf_jump_user((void *)priv->msg_disp,
						(void *)((uintptr_t)stk - 4), read_eflags() | FL_IF);
	 }
}

void
uthread_syscall_switch_back_hook(uint32_t __unused, uint32_t sys_ret)
{
	 uthread_trap_sys_switch_back_hook((struct tf_sys_s *)(&sys_ret));
}

int
uthread_kmsg_enqueue(uint32_t msg)
{
	 struct uthread_priv_s *priv = (struct uthread_priv_s *)proc_priv_get();
	 intr_irq_save();
	 ((uint32_t *)priv->msg_buf)[priv->msg_tail] = msg;
	 priv->msg_tail = (priv->msg_tail + 1) % priv->msg_buf_cap;
	 intr_irq_restore();
	 /* TODO buf full? */
	 return 0;
}

void
sc_upriv_set(uintptr_t upriv)
{
	 struct uthread_priv_s *priv = (struct uthread_priv_s *)proc_priv_get();
	 priv->upriv = upriv;
}

void
sc_uthread_msg_dispatcher_init(uintptr_t disp)
{
	 struct uthread_priv_s *priv = (struct uthread_priv_s *)proc_priv_get();
	 priv->msg_disp = disp;
	 priv->msg_busy = 0;
}

void
sc_uthread_msg_dispatcher_ret(uintptr_t head, int busy)
{
	 struct uthread_priv_s *priv = (struct uthread_priv_s *)proc_priv_get();
	 if (priv->in_msg && priv->msg_disp)
	 {
		  priv->msg_head = head;
		  priv->in_msg   = 0;
		  priv->msg_busy = busy;
		  struct uthread_msg_disp_arg_s *stk = (struct uthread_msg_disp_arg_s *)
			   (priv->msg_disp_stk - sizeof(struct uthread_msg_disp_arg_s));

		  if (stk->from_trapframe)
		  {
			   /* FOR SAFTY */
			   stk->tf.hw.cs     = GD_USER_TEXT  | 3;
			   stk->tf.hw.stk.ss = GD_USER_DATA  | 3;
			   stk->tf.hw.eflags = read_eflags() | FL_IF;
			   stk->tf.es = stk->tf.ds = GD_USER_DATA | 3;
			   
			   uthread_trapframe_switch_back_hook(NULL);
			   ekf_jump_user_from_trapframe(&stk->tf);
		  }
		  else
		  {
			   /* FOR SAFTY */
			   stk->sys.hw.cs     = GD_USER_TEXT  | 3;
			   stk->sys.hw.stk.ss = GD_USER_DATA  | 3;
			   stk->sys.hw.eflags = read_eflags() | FL_IF;

			   uthread_trap_sys_switch_back_hook(NULL);
			   ekf_jump_user_from_trap_sys(&stk->sys);
		  }
	 }
	 ekf_kprintf("SHOULD NOT GET HERE!\n");
	 while (1) ;
}

void
sc_uthread_msg_clean_busy(void)
{
	 struct uthread_priv_s *priv = (struct uthread_priv_s *)proc_priv_get();
	 priv->msg_busy = 0;
}

void
sc_uthread_create(const char *name, uintptr_t entry, uintptr_t upriv, uintptr_t ustack_top)
{
	 proc_attach(uthread_create(name, 0, 0, proc_kvpt_get(), entry, upriv, ustack_top, 0));
}
